import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, filterEmpty, getSlots } from '../../_util/props-util'
import { cloneVNodes, cloneElement } from '../../_util/vnode'
export function toTitle (title) {
  if (typeof title === 'string') {
    return title
  }
  return null
}

export function getValuePropValue (child) {
  const props = getAllProps(child)
  if ('value' in props) {
    return props.value
  }
  if (getKey(child) !== undefined) {
    return getKey(child)
  }
  throw new Error(`no key or value for ${child}`)
}

export function getPropValue (child, prop) {
  if (prop === 'value') {
    return getValuePropValue(child)
  }
  const slots = getSlots(child)
  if (prop === 'children') {
    const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
    if (newChild.length === 1 && !newChild[0].tag) {
      return newChild[0].text
    }
    return newChild
  }
  if (slots[prop]) {
    return cloneVNodes(slots[prop], true)
  }
  const data = getPropsData(child)
  if (prop in data) {
    return data[prop]
  } else {
    return getAttrs(child)[prop]
  }
}

export function isMultiple (props) {
  return !!(props.multiple || props.treeCheckable)
}

export function toArray (value) {
  let ret = value
  if (value === undefined) {
    ret = []
  } else if (!Array.isArray(value)) {
    ret = [value]
  }
  return ret
}

export function preventDefaultEvent (e) {
  e.preventDefault()
}

export const UNSELECTABLE_STYLE = {
  userSelect: 'none',
  WebkitUserSelect: 'none',
}

export const UNSELECTABLE_ATTRIBUTE = {
  unselectable: 'unselectable',
}

export function labelCompatible (prop) {
  let newProp = prop
  if (newProp === 'label') {
    newProp = 'title'
  }
  return newProp
}

export function isInclude (smallArray, bigArray) {
  // attention: [0,0,1] [0,0,10]
  return smallArray.every((ii, i) => {
    return ii === bigArray[i]
  })
}

export function isPositionPrefix (smallPos, bigPos) {
  if (!bigPos || !smallPos) {
    // console.log(smallPos, bigPos);
    return false
  }
  if (bigPos.length < smallPos.length) {
    return false
  }
  // attention: "0-0-1" "0-0-10"
  if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
    return false
  }
  return bigPos.substr(0, smallPos.length) === smallPos
}

/*
export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
  const nodeKey = node.props.eventKey;
  let newCks = [...checkedKeys];
  let nodePos;
  const unCheck = allCheckedNodesKeys.some(item => {
    if (item.key === nodeKey) {
      nodePos = item.pos;
      return true;
    }
  });
  if (unCheck) {
    newCks = [];
    allCheckedNodesKeys.forEach(item => {
      if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
        return;
      }
      newCks.push(item.key);
    });
  } else {
    newCks.push(nodeKey);
  }
  return newCks;
}
*/

function getChildrenlength (children) {
  let len = 1
  if (Array.isArray(children)) {
    len = children.length
  }
  return len
}

function getSiblingPosition (index, len, siblingPosition) {
  if (len === 1) {
    siblingPosition.first = true
    siblingPosition.last = true
  } else {
    siblingPosition.first = index === 0
    siblingPosition.last = index === len - 1
  }
  return siblingPosition
}

function filterChild (childs) {
  const newChilds = []
  childs.forEach(child => {
    const options = getSlotOptions(child)
    if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
      newChilds.push(child)
    }
  })
  return newChilds
}

export function loopAllChildren (childs, callback, parent) {
  const loop = (children, level, _parent) => {
    const len = getChildrenlength(children)
    children.forEach(function handler(item, index) { // eslint-disable-line
      const pos = `${level}-${index}`
      if (item && item.componentOptions && item.componentOptions.children) {
        loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
      }
      if (item) {
        callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
      }
    })
  }
  loop(filterChild(childs), 0, parent)
}

// export function loopAllChildren(childs, callback) {
//   const loop = (children, level) => {
//     React.Children.forEach(children, (item, index) => {
//       const pos = `${level}-${index}`;
//       if (item && item.props.children) {
//         loop(item.props.children, pos);
//       }
//       if (item) {
//         callback(item, index, pos, getValuePropValue(item));
//       }
//     });
//   };
//   loop(childs, 0);
// }

// TODO: Here has the side effect. Update node children data affect.
export function flatToHierarchy (arr) {
  if (!arr.length) {
    return arr
  }
  const hierarchyNodes = []
  const levelObj = {}
  arr.forEach((item) => {
    if (!item.pos) {
      return
    }
    const posLen = item.pos.split('-').length
    if (!levelObj[posLen]) {
      levelObj[posLen] = []
    }
    levelObj[posLen].push(item)
  })
  const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
  // const s = Date.now();
  // todo: there are performance issues!
  levelArr.reduce((pre, cur) => {
    if (cur && cur !== pre) {
      levelObj[pre].forEach((item) => {
        let haveParent = false
        levelObj[cur].forEach((ii) => {
          if (isPositionPrefix(ii.pos, item.pos)) {
            haveParent = true
            if (!ii.children) {
              ii.children = []
            }
            ii.children.push(item)
          }
        })
        if (!haveParent) {
          hierarchyNodes.push(item)
        }
      })
    }
    return cur
  })
  // console.log(Date.now() - s);
  return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
}

// arr.length === 628, use time: ~20ms
export function filterParentPosition (arr) {
  const levelObj = {}
  arr.forEach((item) => {
    const posLen = item.split('-').length
    if (!levelObj[posLen]) {
      levelObj[posLen] = []
    }
    levelObj[posLen].push(item)
  })
  const levelArr = Object.keys(levelObj).sort()
  for (let i = 0; i < levelArr.length; i++) {
    if (levelArr[i + 1]) {
      levelObj[levelArr[i]].forEach(ii => {
        for (let j = i + 1; j < levelArr.length; j++) {
          levelObj[levelArr[j]].forEach((_i, index) => {
            if (isPositionPrefix(ii, _i)) {
              levelObj[levelArr[j]][index] = null
            }
          })
          levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
        }
      })
    }
  }
  let nArr = []
  levelArr.forEach(i => {
    nArr = nArr.concat(levelObj[i])
  })
  return nArr
}
// console.log(filterParentPosition(
// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
// ));

function stripTail (str) {
  const arr = str.match(/(.+)(-[^-]+)$/)
  let st = ''
  if (arr && arr.length === 3) {
    st = arr[1]
  }
  return st
}
function splitPosition (pos) {
  return pos.split('-')
}

// todo: do optimization.
export function handleCheckState (obj, checkedPositionArr, checkIt) {
  // console.log(stripTail('0-101-000'));
  // let s = Date.now();
  let objKeys = Object.keys(obj)

  objKeys.forEach((i, index) => {
    const iArr = splitPosition(i)
    let saved = false
    checkedPositionArr.forEach((_pos) => {
      const _posArr = splitPosition(_pos)
      if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
        obj[i].halfChecked = false
        obj[i].checked = checkIt
        objKeys[index] = null
      }
      if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
        saved = true
      }
    })
    if (!saved) {
      objKeys[index] = null
    }
  })
  objKeys = objKeys.filter(i => i) // filter non null;

  for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
    // loop to set ancestral nodes's `checked` or `halfChecked`
    const loop = (__pos) => {
      const _posLen = splitPosition(__pos).length
      if (_posLen <= 2) { // e.g. '0-0', '0-1'
        return
      }
      let sibling = 0
      let siblingChecked = 0
      const parentPosition = stripTail(__pos)
      objKeys.forEach((i /* , index*/) => {
        const iArr = splitPosition(i)
        if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
          sibling++
          if (obj[i].checked) {
            siblingChecked++
            const _i = checkedPositionArr.indexOf(i)
            if (_i > -1) {
              checkedPositionArr.splice(_i, 1)
              if (_i <= pIndex) {
                pIndex--
              }
            }
          } else if (obj[i].halfChecked) {
            siblingChecked += 0.5
          }
          // objKeys[index] = null;
        }
      })
      // objKeys = objKeys.filter(i => i); // filter non null;
      const parent = obj[parentPosition]
      // not check, checked, halfChecked
      if (siblingChecked === 0) {
        parent.checked = false
        parent.halfChecked = false
      } else if (siblingChecked === sibling) {
        parent.checked = true
        parent.halfChecked = false
      } else {
        parent.halfChecked = true
        parent.checked = false
      }
      loop(parentPosition)
    }
    loop(checkedPositionArr[pIndex], pIndex)
  }
  // console.log(Date.now()-s, objKeys.length, checkIt);
}

function getCheck (treeNodesStates, checkedPositions) {
  const halfCheckedKeys = []
  const checkedKeys = []
  const checkedNodes = []
  Object.keys(treeNodesStates).forEach((item) => {
    const itemObj = treeNodesStates[item]
    if (itemObj.checked) {
      checkedKeys.push(itemObj.key)
      // checkedNodes.push(getValuePropValue(itemObj.node));
      checkedNodes.push({ ...itemObj, pos: item })
    } else if (itemObj.halfChecked) {
      halfCheckedKeys.push(itemObj.key)
    }
  })
  return {
    halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
  }
}

export function getTreeNodesStates (children, values) {
  const checkedPositions = []
  const treeNodesStates = {}
  loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
    treeNodesStates[pos] = {
      node: item,
      key: keyOrPos,
      checked: false,
      halfChecked: false,
      siblingPosition,
    }
    if (values.indexOf(getValuePropValue(item)) !== -1) {
      treeNodesStates[pos].checked = true
      checkedPositions.push(pos)
    }
  })

  handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)

  return getCheck(treeNodesStates, checkedPositions)
}

// can add extra prop to every node.
export function recursiveCloneChildren (children, cb = ch => ch) {
  // return React.Children.map(children, child => {
  return Array.from(children).map(child => {
    const newChild = cb(child)
    if (newChild && newChild.props && newChild.props.children) {
      return cloneElement(newChild, {
        children: recursiveCloneChildren(newChild.props.children, cb),
      })
    }
    return newChild
  })
}
// const newChildren = recursiveCloneChildren(children, child => {
//   const extraProps = {};
//   if (child && child.type && child.type.xxx) {
//     extraProps._prop = true;
//     return React.cloneElement(child, extraProps);
//   }
//   return child;
// });

function recursiveGen (children, level = 0) {
  return children.map((child, index) => {
    const pos = `${level}-${index}`
    const props = getAllProps(child)
    const { title, label, value, ...rest } = props
    const { children: subChildren } = child.componentOptions
    const o = {
      ...rest,
      title,
      label: label || title,
      value,
      key: child.key,
      _pos: pos,
    }
    if (subChildren) {
      o.children = recursiveGen(subChildren, pos)
    }
    return o
  })
}

function recursive (children, cb) {
  children.forEach(item => {
    cb(item)
    if (item.children) {
      recursive(item.children, cb)
    }
  })
}

// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
// If one node checked, it's all children nodes checked.
// If sibling nodes all checked, the parent checked.
export function filterAllCheckedData (vs, treeNodes) {
  const vals = [...vs]
  if (!vals.length) {
    return vals
  }

  const data = recursiveGen(treeNodes)
  const checkedNodesPositions = []

  function checkChildren (children) {
    children.forEach(item => {
      if (item.__checked) {
        return
      }
      const ci = vals.indexOf(item.value)
      const childs = item.children
      if (ci > -1) {
        item.__checked = true
        checkedNodesPositions.push({ node: item, pos: item._pos })
        vals.splice(ci, 1)
        if (childs) {
          recursive(childs, child => {
            child.__checked = true
            checkedNodesPositions.push({ node: child, pos: child._pos })
          })
        }
      } else {
        if (childs) {
          checkChildren(childs)
        }
      }
    })
  }

  function checkParent (children, parent = { root: true }) {
    let siblingChecked = 0
    children.forEach(item => {
      const childs = item.children
      if (childs && !item.__checked && !item.__halfChecked) {
        const p = checkParent(childs, item)
        if (p.__checked) {
          siblingChecked++
        } else if (p.__halfChecked) {
          siblingChecked += 0.5
        }
      } else if (item.__checked) {
        siblingChecked++
      } else if (item.__halfChecked) {
        siblingChecked += 0.5
      }
    })
    const len = children.length
    if (siblingChecked === len) {
      parent.__checked = true
      checkedNodesPositions.push({ node: parent, pos: parent._pos })
    } else if (siblingChecked < len && siblingChecked > 0) {
      parent.__halfChecked = true
    }
    if (parent.root) {
      return children
    }
    return parent
  }
  checkChildren(data)
  checkParent(data)

  checkedNodesPositions.forEach((i, index) => {
    // clear private metadata
    delete checkedNodesPositions[index].node.__checked
    delete checkedNodesPositions[index].node._pos
    // create the same structure of `onCheck`'s return.
    checkedNodesPositions[index].node.props = {
      title: checkedNodesPositions[index].node.title,
      label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
      value: checkedNodesPositions[index].node.value,
    }
    if (checkedNodesPositions[index].node.children) {
      checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
    }
    delete checkedNodesPositions[index].node.title
    delete checkedNodesPositions[index].node.label
    delete checkedNodesPositions[index].node.value
    delete checkedNodesPositions[index].node.children
  })
  return checkedNodesPositions
}

export function processSimpleTreeData (treeData, format) {
  function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
    const children = []
    for (let i = 0; i < array.length; i++) {
      array[i] = { ...array[i] } // copy, can not corrupts original data
      if (array[i][format.pId] === parent[format.id]) {
        array[i].key = array[i][format.id]
        children.push(array[i])
        array.splice(i--, 1)
      }
    }
    if (children.length) {
      parent.children = children
      children.forEach(child => unflatten2(array, child))
    }
    if (parent[format.id] === format.rootPId) {
      return children
    }
  }
  return unflatten2(treeData)
}

export function saveRef (instance, name) {
  if (!instance.saveRefs) {
    instance.saveRefs = {}
  }
  if (!instance.saveRefs[name]) {
    instance.saveRefs[name] = (node) => {
      instance[name] = node
    }
  }
  return instance.saveRefs[name]
}
